#include <iostream>
#include <list>
#include <string>
#include <unordered_map>

using namespace std;

class ISubscriber;
class IPublisher;
class IBroker;

// 订阅者的接口类
class ISubscriber
{
public:
    // 向代理进行订阅
    virtual void subscribeToBroker(IBroker * ptr, string topic) = 0;

    // 向代理取消订阅
    virtual void unsubscribeToBroker(IBroker * ptr, string topic) = 0;
    
    // 当代理收到发布者的通知，就调用这个函数来通知订阅者
    virtual void updateCallback(string topic, int newVal) = 0;
};

// 发布者的接口类
class IPublisher
{
public:
    // 注册一个代理
    virtual void registerBroker(IBroker * pBroker) = 0;

    // 注销一个代理
    virtual void logoutBroker(IBroker * pBroker) = 0;
    
    // 通知代理某个topic上出现新值
    virtual void publishToBroker(string topic, int newData) = 0;
    
    // 发布者的更新函数
    virtual void update(string topic, int newData) = 0;
};

// 代理的接口类
class IBroker
{
public:
    // 添加订阅者
    virtual void addSubscriber(ISubscriber * ptr, string topic) = 0;

    // 移除订阅者
    virtual void removeSubscriber(ISubscriber * ptr, string topic) = 0;
    
    // 向发布者进行注册
    virtual void registerToPublisher(IPublisher * ptr) = 0;

    // 向发布者进行注销
    virtual void logoutToPublisher(IPublisher * ptr) = 0;
    
    // 当发布者通知代理时，调用此函数
    virtual void onPublish(string topic, int newData) = 0;
};

class Broker : public IBroker
{
public:
    virtual void addSubscriber(ISubscriber * ptr, string topic)
    {
        m_subscribersMap[topic].push_back(ptr);
    }

    virtual void removeSubscriber(ISubscriber * ptr, string topic)
    {
        m_subscribersMap[topic].remove(ptr);
    }

    virtual void registerToPublisher(IPublisher * ptr)
    {
        ptr->registerBroker(this);
        m_pPublishers.push_back(ptr);
    }

    virtual void logoutToPublisher(IPublisher * ptr)
    {
        ptr->logoutBroker(this);
        m_pPublishers.remove(ptr);
    }

    virtual void onPublish(string topic, int newData)
    {
        for (list<ISubscriber *>::iterator it = m_subscribersMap[topic].begin(); it != m_subscribersMap[topic].end(); it++)
        {
            (*it)->updateCallback(topic, newData);
        }
    }

private:
    list<IPublisher*> m_pPublishers;
    unordered_map<string, list<ISubscriber*> > m_subscribersMap;
};


class Subscriber : public ISubscriber
{
public:
    Subscriber(const string & name):m_name(name) {}

    virtual ~Subscriber() {}

    virtual void subscribeToBroker(IBroker * ptr, string topic)
    {
        m_topicMap[topic] = 0;
        ptr->addSubscriber(this, topic);
    }

    virtual void unsubscribeToBroker(IBroker * ptr, string topic)
    {
        m_topicMap.erase(topic);
        ptr->removeSubscriber(this, topic);
    }

    virtual void updateCallback(string topic, int newData)
    {
        unordered_map<string, int>::iterator it = m_topicMap.find(topic);
        if (it != m_topicMap.end()) {
            it->second = newData;
            cout << m_name << ": new data for topic " << topic <<  " is " << newData << endl;
        } else {
            cout << m_name << ": unregistered topic " << topic << endl;
        }
    }

private:
    string m_name;
    unordered_map<string, int> m_topicMap;
};

class Publisher : public IPublisher
{
public:
    virtual ~Publisher() {
    
    }

    virtual void registerBroker(IBroker * pBroker)
    {
        m_brokerPtr = pBroker;
    }

    virtual void logoutBroker(IBroker * pBroker)
    {
        m_brokerPtr = NULL;
    }

    virtual void publishToBroker(string topic, int newData)
    {
        m_brokerPtr->onPublish(topic, newData);
    }

    virtual void update(string topic, int newData)
    {
        publishToBroker(topic, newData);
    }

private:
    IBroker *m_brokerPtr;
};



int main()
{
    Broker broker;
    Publisher pub;
    broker.registerToPublisher(&pub);

    Subscriber sub1("sub1");
    Subscriber sub2("sub2");

    sub1.subscribeToBroker(&broker, "AA");
    sub2.subscribeToBroker(&broker, "AA");
    sub1.subscribeToBroker(&broker, "BB");
    sub2.subscribeToBroker(&broker, "BB");

    cout << "=================" << endl;
    pub.update("AA", 1);
    pub.update("BB", 1);
    pub.update("AA", 2);
    pub.update("BB", 2);
    cout << "=================" << endl;

    sub1.unsubscribeToBroker(&broker, "AA");
    pub.update("AA", 3);
    pub.update("BB", 3);
    pub.update("AA", 4);
    pub.update("BB", 4);
    cout << "=================" << endl;

    sub1.unsubscribeToBroker(&broker, "BB");
    sub2.unsubscribeToBroker(&broker, "AA");
    pub.update("AA", 5);
    pub.update("BB", 5);
    pub.update("AA", 6);
    pub.update("BB", 6);
    cout << "=================" << endl;

    return 0;
}